#include <stdlib.h>

#include "util.h"
#include "prctoken.h"
#include "log/pear_log.h"
#include "utils/pear_dump.h"
#include "account/pear_account.h"
#include "block/pear_block_ctx.h"
#include "transaction/pear_tx_ctx.h"
#include "transaction/pear_tx_serial.h"
#include "validator/pear_validator.h"

#define VERSION "1.0.0"

void *PRCTokenApp(Application *, Types__Request *);

PRCToken_t *pr_new_PRCToken()
{
    PRCToken_t *p = NULL;

    p = (PRCToken_t *) malloc(sizeof(*p));
    if (p == NULL)
    {
        PEAR_LOG("Malloc PRCToken error\n");
        return NULL;
    }

    p->tx_ctx      = pr_new_tx_ctx(0);
    p->acc_ctx     = pr_new_acc_ctx();
    p->blk_ctx     = pr_new_block_ctx();
    p->app.handler = &PRCTokenApp;
    p->valid_ctx   = pr_new_validator_ctx();

    return p;
}


static Types__ResponseInfo *Info(Application *app)
{
    unsigned char *hash = NULL;
    PRCToken_t     *prct    = (PRCToken_t *)app;
    pr_block_ctx_t *blk_ctx = prct->blk_ctx;

	Types__ResponseInfo *info = NULL;
	info = response_malloc_info();
	if ( info == NULL )
	{
		return NULL;
	}

	info->version = (char*)malloc(strlen(VERSION)+1);
	if (info->version == NULL)
	{
		response_free_info(info);
		return NULL;
	}

	memset(info->version, 0, strlen(VERSION)+1);

	info->has_last_block_height = 1;
	info->last_block_height = pr_query_height(blk_ctx);

	if (info->last_block_height > 0)
	{
		info->has_last_block_app_hash = 1;

        hash = pr_last_block_hash(blk_ctx);

		info->last_block_app_hash.data = (unsigned char *) malloc(32);
		if (info->last_block_app_hash.data == NULL)
		{
			response_free_info(info);
			return NULL;
		}

		info->last_block_app_hash.len = 32;
		memcpy(info->last_block_app_hash.data, hash, 32);
	}
	else
	{
		info->has_last_block_height   = 0;
		info->has_last_block_app_hash = 0;
	}

	return info;
}

/*
 * Store Tx And Return Tx Hash
 *
 */
static Types__ResponseDeliverTx *DeliverTx(Types__RequestDeliverTx *req, Application *app)
{
	int     *cols = 0;
	char    *log  = NULL;
	uint8_t *hash = NULL;
	uint8_t words[2][64] = {};

    PRCToken_t *prct = (PRCToken_t *)app;

	Types__CodeType code = 0;
	Types__ResponseDeliverTx *deliver_tx = NULL;

	if ( req->has_tx )
	{
        //printf("txstr pr_tx_encode: %s\n", pr_tx_encode(req->tx.data));
        if (prct->tx_ctx->deliver_tx(prct->acc_ctx, req->tx.data, req->tx.len) < 0)
        {
            code == TYPES__CODE_TYPE__OK; //ERR
        }
        else
        {
            code == TYPES__CODE_TYPE__OK;
        }
	}

	deliver_tx = response_malloc_delivertx();
	if (deliver_tx == NULL)
	{
		return NULL;
	}


	deliver_tx->has_code = 1;
	deliver_tx->code = code;

	if ( code != TYPES__CODE_TYPE__OK )
		log = "InternalError";
	else
		log = "OK";

	deliver_tx->log = (char*)malloc(strlen(log)+1);
	if ( deliver_tx->log == NULL )
	{
		response_free_delivertx(deliver_tx);
		return NULL;
	}
	memset(deliver_tx->log, 0, strlen(log)+1);
	memcpy(deliver_tx->log, log, strlen(log));

	hash = get_last_app_hash();
	if ( hash != NULL )
	{
		deliver_tx->has_data = 1;
		deliver_tx->data.data = (uint8_t*)malloc(HASHLEN);
		if ( deliver_tx->data.data == NULL )
		{
			response_free_delivertx(deliver_tx);
			return NULL;
		}
		deliver_tx->data.len = HASHLEN;
		memcpy(deliver_tx->data.data, hash, HASHLEN);
	}

	return deliver_tx;
}

static Types__ResponseCheckTx *CheckTx(Types__RequestCheckTx *req, Application *app)
{
    char    *txHex = NULL;
	uint8_t *log   = NULL;
	uint8_t *hash  = NULL;
	Types__CodeType code = 0;
	Types__ResponseCheckTx *checktx = NULL;

    PRCToken_t *prct = (PRCToken_t *)app;

	if ( req->has_tx )
	{
        prct->tx_ctx->check_tx(prct->acc_ctx, req->tx.data, req->tx.len);
		code = check_transation(req->tx.data);
	}

	checktx = response_malloc_checktx();
	if ( checktx == NULL )
	{
		return NULL;
	}

	checktx->code = code;
	checktx->has_code = 1;

	if ( code != TYPES__CODE_TYPE__OK )
		log = "InternalError";
	else
		log = "OK";

	checktx->log = (char*)malloc(strlen(log)+1);
	if (checktx->log == NULL)
	{
		response_free_checktx(checktx);
		return NULL;
	}

	memset(checktx->log, 0, strlen(log)+1);
	memcpy(checktx->log, log, strlen(log));

	checktx->has_data = 1;
	hash = get_last_app_hash();
	if ( hash != NULL )
	{
		checktx->data.data =(uint8_t*)malloc(HASHLEN);
		if ( checktx->data.data == NULL )
		{
			response_free_checktx(checktx);
			return NULL;
		}
		memcpy(checktx->data.data, hash, HASHLEN);
		checktx->data.len = HASHLEN;
	}

	return checktx;
}


/*
 * Response Last Tx AppHash
 *
 */
static Types__ResponseCommit *Commit(Application *app)
{
    int log_len = 0;
	uint8_t        *log = "OK";
    pr_block_t     *blk = NULL;
    unsigned char *hash = NULL;
    //unsigned char hash[32] = {0x00};
	//Types__CodeType code = 0;
	Types__ResponseCommit *commit = NULL;

    PRCToken_t     *prct    = (PRCToken_t *)app;
    pr_block_ctx_t *blk_ctx = prct->blk_ctx;

	commit = response_malloc_commit();
	if ( commit == NULL )
	{
		return NULL;
	}

	commit->has_code = 1;
	commit->code = TYPES__CODE_TYPE__OK ;

	commit->log = (char*)malloc(strlen(log)+1);
	if ( commit->log == NULL )
	{
		response_free_commit(commit);
		return NULL;
	}

    log_len = strlen(log);
	memset(commit->log, 0, log_len + 1);
	memcpy(commit->log, log, log_len);
    
    blk = pr_new_block(blk_ctx);
    if (blk == NULL)
    {
        PEAR_LOG("pr_new_block error\n");
        return commit;
    }
    
    commit->has_data  = 1;
    commit->data.data = (unsigned char *)malloc(32);
    if ( commit->data.data == NULL )
    {
        response_free_commit(commit);
        return NULL;
    }

    //pr_block_hash(blk_ctx, blk, hash, sizeof(hash));
    hash = pr_last_block_hash(blk_ctx);
    //memcpy(commit->data.data, hash, sizeof(hash));
    memcpy(commit->data.data, hash, 32);
    commit->data.len = sizeof(hash);

	return commit;
}

/* ÏÂÃæº¯Êý²»ÊµÏÖ */
static Types__ResponseQuery *Query(Types__RequestQuery *req)
{
	return NULL;
}

static Types__ResponseSetOption *SetOption(Types__RequestSetOption *req )
{
	return NULL;
}

static char *pear_BIN_to_XX(unsigned char *bin, char *xx_str, int bin_len)
{
    int i = 0;

    for (; i < bin_len; i++)
    {
        sprintf(xx_str + i * 2, "%02x", bin[i]);
    }

    return xx_str;
}

static Types__ResponseInitChain *InitChain(Types__RequestInitChain *req, Application *app)
{
    int   i   = 0;
    char *buf = NULL;
    Types__Validator *temp = NULL;

    PRCToken_t *prct = (PRCToken_t *)app;

    printf("validators num: %ld\n", req->n_validators);

    for (; i < req->n_validators; i++)
    {
        PEAR_LOG("Store Validators Start\n");
        temp = req->validators[i];

        if (temp->has_pubkey)
        {
            buf = (char *) malloc(temp->pubkey.len * sizeof(char) * 2);

            PEAR_LOG("pubkey: %s\n", pr_hex_encode(buf, temp->pubkey.data, temp->pubkey.len));

            if (pr_validator_add(prct->valid_ctx, buf, temp->power) < 0)
            {
                continue;
            }
        }
    }

    PEAR_LOG("Store Validators End\n");

	return NULL;
}

static Types__ResponseBeginBlock *BeginBlock(Types__RequestBeginBlock *req)
{
	return NULL;
}

static Types__ResponseEndBlock *EndBlock(Types__RequestEndBlock *req)
{
	return NULL;
}


void *PRCTokenApp(Application *app, Types__Request *request)
{
	switch( request->value_case )
	{
		case TYPES__REQUEST__VALUE_INFO:
			return Info(app);
		case TYPES__REQUEST__VALUE_SET_OPTION:
			return SetOption(request->set_option);
		case TYPES__REQUEST__VALUE_DELIVER_TX:
			return DeliverTx(request->deliver_tx, app);
		case TYPES__REQUEST__VALUE_CHECK_TX:
			return CheckTx(request->check_tx, app);
		case TYPES__REQUEST__VALUE_COMMIT:
			return  Commit(app);
		case TYPES__REQUEST__VALUE_QUERY:
			return Query(request->query);
		case TYPES__REQUEST__VALUE_INIT_CHAIN:
			return InitChain(request->init_chain, app);
		case TYPES__REQUEST__VALUE_BEGIN_BLOCK:
			return BeginBlock(request->begin_block);
		case TYPES__REQUEST__VALUE_END_BLOCK:
			return EndBlock(request->end_block);
	}
}

